/*
 * libnetlink.c	RTnetlink service routines.
 *
 *      source code from:iproute2 ipaddress.c
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 *
 * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <fcntl.h>
#include <net/if_arp.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/uio.h>

#include "libnetlink.h"
#include "sysutil.h"

// This function is to open the netlink socket as the name suggests.
int rtnl_open(struct rtnl_handle *rth)
{
    int addr_len;

    memset(rth, 0, sizeof(*rth));

    // Creating the netlink socket of family NETLINK_ROUTE

    rth->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (rth->fd < 0)
    {
        perror("cannot open netlink socket");
        return -1;
    }
    memset(&rth->local, 0, sizeof(rth->local));
    rth->local.nl_family = AF_NETLINK;
    rth->local.nl_groups = 0;

    // Binding the netlink socket
    if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)) < 0)
    {
        perror("cannot bind netlink socket");
        return -1;
    }
    addr_len = sizeof(rth->local);
    if (getsockname(rth->fd, (struct sockaddr*)&rth->local, (socklen_t*) &addr_len) < 0)
    {
        perror("cannot getsockname");
        return -1;
    }
    if (addr_len != sizeof(rth->local))
    {
        fprintf(stderr, "wrong address lenght %d\n", addr_len);
        return -1;
    }
    if (rth->local.nl_family != AF_NETLINK)
    {
        fprintf(stderr, "wrong address family %d\n", rth->local.nl_family);
        return -1;
    }
    rth->seq = gettime();
    return 0;
}

// This function does the actual reading and writing to the netlink socket
int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer,
        unsigned groups, struct nlmsghdr *answer)
{
    int status;
    struct sockaddr_nl nladdr;
    // Forming the iovector with the netlink packet.
    struct iovec iov = { (void*)n, n->nlmsg_len };
    // Forming the message to be sent.
    struct msghdr msg = { (void*)&nladdr, sizeof(nladdr), &iov, 1, NULL, 0, 0 };
    // Filling up the details of the netlink socket to be contacted in the
    // kernel.
    memset(&nladdr, 0, sizeof(nladdr));
    nladdr.nl_family = AF_NETLINK;
    nladdr.nl_pid = peer;
    nladdr.nl_groups = groups;
    n->nlmsg_seq = ++rtnl->seq;
    if (answer == NULL)
        n->nlmsg_flags |= NLM_F_ACK;
    // Actual sending of the message, status contains success/failure
    status = sendmsg(rtnl->fd, &msg, 0);
    if (status < 0)
        return -1;

    return 0;
}

void rtnl_close(struct rtnl_handle *rth)
{
    if (rth->fd >= 0) {
        close(rth->fd);
        rth->fd = -1;
    }
}

// This is the utility function for adding the parameters to the packet.
int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen)
{
    int len = RTA_LENGTH(alen);
    struct rtattr *rta;

    if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen)
        return -1;
    rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len));
    rta->rta_type = type;
    rta->rta_len = len;
    memcpy(RTA_DATA(rta), data, alen);
    n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;
    return 0;
}

int get_unsigned(unsigned *val, const char *arg, int base)
{
    unsigned long res;
    char *ptr;

    if (!arg || !*arg)
        return -1;

    res = strtoul(arg, &ptr, base);

    /* empty string or trailing non-digits */
    if (!ptr || ptr == arg || *ptr)
        return -1;

    /* overflow */
    if (res == ULONG_MAX && errno == ERANGE)
        return -1;

    /* out side range of unsigned */
    if (res > UINT_MAX)
        return -1;

    *val = res;
    return 0;
}

int mask2bits(__u32 netmask)
{
    unsigned bits = 0;
    __u32 mask = ntohl(netmask);
    __u32 host = ~mask;

    /* a valid netmask must be 2^n - 1 */
    if ((host & (host + 1)) != 0)
        return -1;

    for (; mask; mask <<= 1)
        ++bits;
    return bits;
}

/* This uses a non-standard parsing (ie not inet_aton, or inet_pton)
 * because of legacy choice to parse 10.8 as 10.8.0.0 not 10.0.0.8
 */
static int get_addr_ipv4(__u8 *ap, const char *cp)
{
    int i;

    for (i = 0; i < 4; i++) {
        unsigned long n;
        char *endp;

        n = strtoul(cp, &endp, 0);
        if (n > 255)
            return -1;      /* bogus network value */

        if (endp == cp) /* no digits */
            return -1;

        ap[i] = n;

        if (*endp == '\0')
            break;

        if (i == 3 || *endp != '.')
            return -1;      /* extra characters */
        cp = endp + 1;
    }

    return 1;
}

int get_addr_1(inet_prefix *addr, const char *name, int family)
{
    memset(addr, 0, sizeof(*addr));

    if (strcmp(name, "default") == 0 ||
            strcmp(name, "all") == 0 ||
            strcmp(name, "any") == 0) {
        if (family == AF_DECnet)
            return -1;
        addr->family = family;
        addr->bytelen = (family == AF_INET6 ? 16 : 4);
        addr->bitlen = -1;
        return 0;
    }

    if (strchr(name, ':')) {
        addr->family = AF_INET6;
        if (family != AF_UNSPEC && family != AF_INET6)
            return -1;
        if (inet_pton(AF_INET6, name, addr->data) <= 0)
            return -1;
        addr->bytelen = 16;
        addr->bitlen = -1;
        return 0;
    }


    addr->family = AF_INET;
    if (family != AF_UNSPEC && family != AF_INET)
        return -1;

    if (get_addr_ipv4((__u8 *)addr->data, name) <= 0)
        return -1;

    addr->bytelen = 4;
    addr->bitlen = -1;
    return 0;
}

static int get_netmask(unsigned *val, const char *arg, int base)
{
    inet_prefix addr;

    if (!get_unsigned(val, arg, base))
        return 0;

    /* try coverting dotted quad to CIDR */
    if (!get_addr_1(&addr, arg, AF_INET) && addr.family == AF_INET) {
        int b = mask2bits(addr.data[0]);

        if (b >= 0) {
            *val = b;
            return 0;
        }
    }

    return -1;
}

int af_bit_len(int af)
{
    switch (af) {
        case AF_INET6:
            return 128;
        case AF_INET:
            return 32;
    }

    return 0;
}

int get_prefix(inet_prefix *dst, const char *ip, int family)
{
    int err;
    unsigned plen;
    char arg[128], *slash;

    strcpy(arg, ip);
    memset(dst, 0, sizeof(*dst));

    if (strcmp(arg, "default") == 0 ||
            strcmp(arg, "any") == 0 ||
            strcmp(arg, "all") == 0) {
        dst->family = family;
        dst->bytelen = 0;
        dst->bitlen = 0;
        return 0;
    }

    slash = strchr(arg, '/');
    if (slash)
        *slash = 0;

    err = get_addr_1(dst, arg, family);
    if (err == 0) {
        dst->bitlen = af_bit_len(dst->family);

        if (slash) {
            if (get_netmask(&plen, slash+1, 0)
                    || plen > dst->bitlen) {
                err = -1;
                goto done;
            }
            dst->flags |= PREFIXLEN_SPECIFIED;
            dst->bitlen = plen;
        }
    }
done:
    if (slash)
        *slash = '/';
    return err;
}
